package cn.k7g.copyproperties;

import com.intellij.notification.Notification;
import com.intellij.notification.NotificationGroup;
import com.intellij.notification.NotificationGroupManager;
import com.intellij.notification.Notifications;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.ui.MessageType;
import com.intellij.psi.*;

import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;

public class CopyPropertiesAction extends AnAction {

    static NotificationGroupManager instance = NotificationGroupManager.getInstance();
    static NotificationGroup notificationGroup = instance.getNotificationGroup("copy_properties_action.notification.balloon");

    static Notification pleaseSelectionMethod = notificationGroup.createNotification("please selection method", MessageType.ERROR);

    @Override
    public void actionPerformed(AnActionEvent event) {
        PsiElement psiElement = event.getData(CommonDataKeys.PSI_ELEMENT);
        if (psiElement == null) {
            Notifications.Bus.notify(pleaseSelectionMethod);
            return;
        }
        PsiMethod psiMethod = getPsiMethod(psiElement);
        if (psiMethod == null) {
            Notifications.Bus.notify(pleaseSelectionMethod);
            return;
        }

        if (!(psiMethod.getReturnType() instanceof PsiClassType)) {
            Notifications.Bus.notify(notificationGroup.createNotification("invalid return class", MessageType.ERROR));
            return;
        }

        PsiClass target = ((PsiClassType) psiMethod.getReturnType()).resolve();
        PsiClass origin = null;
        String originName = "";
        if (psiElement instanceof PsiVariable) {
            PsiVariable var = (PsiVariable) psiElement;
            origin = ((PsiClassType) var.getType()).resolve();
            originName = var.getName();
        } else if (!psiMethod.getModifierList().hasModifierProperty("static")) {
            origin = psiMethod.getContainingClass();
            originName = "this";
        } else if (psiElement instanceof PsiMethod) {
            PsiParameter parameter = getFirstPsiParameter(psiMethod);
            if (parameter != null) {
                origin = ((PsiClassType) parameter.getType()).resolve();
                originName = parameter.getName();
            }
        }

        if (origin == null) {
            Notifications.Bus.notify(notificationGroup.createNotification("invalid origin object", MessageType.ERROR));
            return;
        }

        StringBuilder codeBlock = new StringBuilder("{\n\n");
        if (!originName.equals("this")) {
            codeBlock.append("if (").append(originName).append(" == null ) { return null; } \n");
        }
        codeBlock.append(target.getName()).append(" ret = new ").append(target.getName()).append("();\n");
        PsiField[] allFields = target.getAllFields();
        Map<String, PsiField> map = Arrays.stream(origin.getAllFields()).collect(Collectors.toMap(k -> k.getName(), v -> v));
        for (PsiField f : allFields) {
            if (f.hasModifierProperty("static")) {
                continue;
            }
            if (map.containsKey(f.getName())) {
                codeBlock.append("ret.").append(getSetMethodName(f)).append("(").append(originName).append(".")
                        .append(getGetMethodName(f)).append("()")
                        .append(");\n");
            }
        }
        codeBlock.append("return ret;");
        codeBlock.append("} \n");


        PsiCodeBlock oldCb = psiMethod.getBody();
        PsiElementFactory factory = JavaPsiFacade.getInstance(event.getProject()).getElementFactory();
        PsiCodeBlock newCodeBlock =  factory.createCodeBlockFromText(codeBlock.toString(), null);


        WriteCommandAction.runWriteCommandAction(event.getProject(), () -> {
            PsiElement[] children = newCodeBlock.getChildren();
            for (int i = 1; i < children.length - 1; i++) {
                oldCb.add(children[i]);
            }
        });
    }

    private String getSetMethodName(PsiField f) {
        return "set" + convertMethodName(f);
    }

    private String getGetMethodName(PsiField f) {
        String prefix = "get";
        if (f.getType().equalsToText("boolean")) {
            prefix = "is";
        }
        return prefix + convertMethodName(f);
    }

    private String convertMethodName(PsiField f) {
        String name = f.getName();
        if (name.charAt(0) >= (int)'a') {
            return (char)(name.charAt(0) - 'a' + 'A') + name.substring(1);
        }
        return name;
    }


    private PsiParameter getFirstPsiParameter(PsiMethod psiMethod) {
        PsiParameterList list = (PsiParameterList) Arrays.stream(psiMethod.getChildren()).filter(item -> item instanceof PsiParameterList).findFirst().orElse(null);
        if (list == null || list.isEmpty()) {
            return null;
        }
        return list.getParameter(0);
    }

    private PsiMethod getPsiMethod(PsiElement psiElement) {
        if (psiElement instanceof PsiMethod) {
            return (PsiMethod) psiElement;
        }
        return getPsiMethod(psiElement.getParent());
    }
}
